DOM 中的 attribute 和 property 的区别

Author Avatar
Klein 8月 12, 2018

看到了这样的面试题,但是给出的答案太简短了。还有很多不太懂的地方。

定义

当浏览器加载页面时,会解析 HTML 文本,并从中生成 DOM 对象。对于元素节点,大多数标准的 HTML 属性(attribute)会自动成为 DOM 对象的属性(property)。

例如,如果标签是 <div id"div1"><div> ,那么 DOM 对象有 div.id"div1" ,在这里 attribute 和 property 具有1:1的映射(同步)的关系,这一点在后面会讨论。

但并不是所有的 attribute 和 property 都有1:1的映射(同步)的关系

DOM property(属性)

property 属性属于 DOM 对象, DOM 实质就是 javascript 中的对象。

1
2
3
window.document.myform.text1
// 等同于这样写
document.myform.text1

DOM 的属性和方法与常规 JavaScript 对象的行为一样,因此,我们可以在 js 中操作普通对象一样获取、设置 DOM 对象的属性。

  • property 属性可以是任意数据类型,
  • property 对大小写敏感。

所以,

自定义的 property 不会出现在 html 代码中,只存在 js 中,

这句话也就不难理解了。

HTML Attribute(特性)

attribute 特性由 HTML 定义,所有出现在 HTML 标签内的描述节点都是 attribute 特性。

在 HTML 语言中,标签可能有 attribute 属性。 当浏览器读取 HTML 文本并为标签创建 DOM 对象时,它会识别标准属性并从中创建 DOM 属性。

因此,当一个元素具有 id 或其他标准属性(attribute)时,相应的属性(property)就会被创建。 但是,如果 attribute 是非标准的,则不会发生这种情况。

1
2
3
4
5
6
7
<body id="test" something="non-standard">
<script>
alert(document.body.id); // test
// non-standard attribute does not yield a property
alert(document.body.something); // undefined
</script>
</body>

注意,一个元素的标准属性对另一个元素来说是未知的。 例如,”type”是输入的标准(HTMLInputElement) ,而不是 body (HTMLBodyElement)。 标准属性在相应元素类的规范中被描述。

1
2
3
4
5
6
7
<body id="test" something="non-standard">
<script>
alert(document.body.id); // test
// non-standard attribute does not yield a property
alert(document.body.something); // undefined
</script>
</body>

因此,如果一个属性是非标准的,那么它就不会有 DOM-property。 有没有方法可以访问这些属性?

  • elem.hasAttribute(name) 检测该 attribute 是否存在
  • elem.getAttribute(name) 获取该 attribute 的值
  • elem.setAttribute(name, value) 设置该 attribute 的值
  • elem.removeAttribute(name) – 移除该 attribute
  • attribute 特性的类型总是字符串类型,
  • 对大小写不敏感。

标准(非自定义) attribute 特性 与 property有1:1的映射(同步)关系,比如:id, class, title, style等

在大多数情况下,当 attribute (或 property)发生更改,相应的property (或 attribute)也会自动同步更新,反之亦然。

1
2
3
4
5
6
7
8
9
10
11
12
13
<input>

<script>
let input = document.querySelector('input');

// attribute => property
input.setAttribute('id', 'id');
alert(input.id); // id (updated)

// property => attribute
input.id = 'newId';
alert(input.getAttribute('id')); // newId (updated)
</script>

例子2:

1
2
3
4
5
6
<div id="test" class="button"></div>
var div = document.getElementById('test');
div.className = 'red-input';
div.getAttribute('class'); // return string: "red-input"
div.setAttribute('class','green-input');
div.className; // return string: "green-input"

注意:当我们通过 property 属性进行设置或获取 class 时,我们需要使用” className “,因为在js中 class 是关键字。

但有例外的情况,比如 value只是从 attribute -> property,单向同步。

1
2
3
4
5
6
7
8
9
10
11
12
13
<input>

<script>
let input = document.querySelector('input');

// attribute => property
input.setAttribute('value', 'text');
alert(input.value); // text

// NOT property => attribute
input.value = 'newValue';
alert(input.getAttribute('value')); // text (not updated!)
</script>

听说?这里还不是很了解。getAttribute 方法有一个潜规则,部分属性(input的value和checked)通过getAttribut取到的是初始值,

DOM property 返回的值是打印的

DOM property 不总是字符串。比如,input.checked 属性(用于复选框)是一个布尔:

1
2
3
4
5
6
<input id="input" type="checkbox" checked> checkbox

<script>
alert(input.getAttribute('checked')); // the attribute value is: empty string
alert(input.checked); // the property value is: true
</script>

还有其他例子:style attribute 是一个字符串, 但是 style property 却是一个对象:

1
2
3
4
5
6
<input id="input" type="checkbox" checked> checkbox

<script>
alert(input.getAttribute('checked')); // the attribute value is: empty string
alert(input.checked); // the property value is: true
</script>

这是一个重要的区别。 而且,即使 DOM property 类型是字符串,它可能与 attribute 不同!
例如,href DOM 属性总是一个完整的 URL,即使该属性包含一个相对的 URL 或者只是一个 # hash。

1
2
3
4
5
6
7
8
<a id="a" href="#hello">link</a>
<script>
// attribute
alert(a.getAttribute('href')); // #hello

// property
alert(a.href ); // full URL in the form http://site.com/page#hello
</script>

所以,如果我们需要 href 的值或者 HTML 中写的任何其他属性,我们可以使用 getAttribute。

非标准属性,dateset

在编写 HTML 时,我们使用了很多标准属性。 但是那些非标准的,定制的呢? 首先,让我们看看它们是否有用? 为什么?

有时非标准属性被用来传递从 HTML 到 JavaScript 的自定义数据,或者用于”标记”JavaScript 的 HTML-elements。

像这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- 标记 这个 div 元素 用来展示 name 信息 -->
<div show-info="name"></div>
<!-- age -->
<div show-info="age"></div>

<script>
// 代码找到带有标记的元素,并显示所请求的内容
let user = {
name: "Pete",
age: 25
};

for(let div of document.querySelectorAll('[show-info]')) {
// 将相应的信息插入字段
let field = div.getAttribute('show-info');
div.innerHTML = user[field]; // Pete, then age
}
</script>

它们还可以用来设计元素。

例如,这里使用了 attribute order-state 的顺序状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<style>
/* 样式依赖自定义 attribute "order-state" */
.order[order-state="new"] {
color: green;
}

.order[order-state="pending"] {
color: blue;
}

.order[order-state="canceled"] {
color: red;
}
</style>

<div class="order" order-state="new">
A new order.
</div>

<div class="order" order-state="pending">
A pending order.
</div>

<div class="order" order-state="canceled">
A canceled order.
</div>

为什么这个 attribute 可能比像这样的 class :.order-state-new, .order-state-pending , order-state-canceled 更好

这是因为一个属性更容易管理。 这种状态可以像以下那样容易改变:

1
2
3
// a bit simpler than removing old/adding a new class
// 比起 删除旧类/添加新类 要简单一点
div.setAttribute('order-state', 'canceled');

为了避免冲突,data-* 属性(attributes)诞生了。
所有以”date-“开头的属性(attributes)都保留给程序员使用。 它们可以在 dataset 属性(property)中使用。

例如,如果 elem 有一个名为 "data-about" 的属性,它可以作为 elem.dataset.about 使用。
像这样:

1
2
3
4
<body data-about="Elephants">
<script>
alert(document.body.dataset.about); // Elephants
</script>

Multiword attributes like data-order-state become camel-cased: dataset.orderState.
多个单词的 attributes 像是这样的 data-order-state , 变成了驼峰写法:dataset.orderState

下面是一个重写的”订单状态”例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<style>
.order[data-order-state="new"] {
color: green;
}

.order[data-order-state="pending"] {
color: blue;
}

.order[data-order-state="canceled"] {
color: red;
}
</style>

<div id="order" class="order" data-order-state="new">
A new order.
</div>

<script>
// read
alert(order.dataset.orderState); // new

// modify
order.dataset.orderState = "pending"; // (*)
</script>

使用 date-* 属性是传递自定义数据的有效、安全的方法。

总结

  • Attribute 是在 HTML 中写的
  • Properties 是 DOM 对象中的内容

使用 attributes 的方法有:

  • elem.hasAttribute(name) 检测该 attribute 是否存在
  • elem.getAttribute(name) 获取该 attribute 的值
  • elem.setAttribute(name, value) 设置该 attribute 的值
  • elem.removeAttribute(name) – 移除该 attribute

对于大多数需求,DOM property 可以很好地为我们服务。 我们只有在 DOM property 不适合我们的时候才会引用 attribute ,例如,当我们需要确切的 attribute 时,例如:

  • 我们需要一个非标准属性。而且是以 date- 开头,我们使用 dateset
  • We want to read the value “as written” in HTML. The value of the DOM property may be different, for instance the href property is always a full URL, and we may want to get the “original” value.
  • 我们想要在写在 HTML 中的值,但是通过 DOM property 获取的这个值和 HTML 中的不一样,例如:href 属性(property) 总是一个完整的 url,而我们想要的是原始的值。

#日常使用

1
2
3
4
<input id="search" value="foo" />
var input = document.getElementById('search');
input.value = 'foo2';
input.getAttribute('value'); // return string: "foo"

也就是说我们平时在写业务的时候多数情况下使用 property 是正确的。当用户在 input 元素输入更改的时候,attribute 特性的 value 属性的值不会变化,即使js更改 value,也不会使 attribute 变化。

在javascript中我们推荐使用property属性因为这个属性相对attribute更快,更简便。尤其是有些类型本该是布尔类型的attribute特性。比如:”checked”, “disabled”, “selected”。浏览器会自动将这些值转变成布尔值传给property属性。

1
<input id="test" class="blue" type="radio" />

好例子:

1
2
3
4
5
6
7
8
9
// get id
document.getElementById('test').id;
// set class
document.getElementById('test').className = 'red';
// get and set radio control status
document.getElementById('test').checked; // boolean
document.getElementById('test').checked = true;
$('#test').prop('checked'); // boolean
$('#test').prop('checked', true);

坏例子:

1
2
3
4
5
// get id
document.getElementById('test').getAttribute('id');
// set class
document.getElementById('test').setAttribute('class', 'red');
document.getElementById('test').getAttribute('checked'); // 返回字符串类型 'checked'

参考资料

输入框的value问题(DOM的property和attribute)
[译]HTML attribute与DOM property之间的区别?
JavaScript中的property和attribute的区别
Attributes and properties
DOM中Property与Attribute的区别
DOM系列:Attribute和Property